<!--
 * @Description: 富文本编辑器
 * @Author: HuiSir<https://gitee.com/huisir001>
 * @Date: 2022-02-16 14:30:17
 * @LastEditTime: 2025-09-25 17:03:47
-->
<template>
  <!-- doc:https://www.wangeditor.com/v5/ -->
  <div>
    <div class="my-rich-editor">
      <Toolbar
        :mode="mode"
        :editor="editorRef"
        :defaultConfig="toolbarConfig"
        class="editor-toolbar"
      />
      <Editor
        :mode="mode"
        :modelValue="htmlVal"
        :defaultConfig="editorConfig"
        @onCreated="handleCreated"
        @onChange="handleChange"
        @onDestroyed="$emit('onDestroyed', $event)"
        @onFocus="$emit('onFocus', $event)"
        @onBlur="$emit('onBlur', $event)"
        @customAlert="customAlert"
        class="editor-container"
      />
    </div>
  </div>
</template>

<script lang="ts" setup>
/* eslint-disable @typescript-eslint/no-explicit-any */
import { computed, onBeforeUnmount, PropType, shallowRef } from "vue";
import { IToolbarConfig } from "@wangeditor/editor";
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
import "@wangeditor/editor/dist/css/style.css";
import toolbarKeys from "./toolbarKeys.json";
import { ElMessage } from "element-plus";
import { RichEditorUploadImageConfig } from "../types";
import { REG_EXP_PATTERN } from "../../helpers/constant";

// 组件名称（必要）
defineOptions({ name: "FyRichEditor" });

const editorVal = defineModel<string>({ default: "" });

// props
const props = defineProps({
  /**
   * 工具欄配置
   */
  toolbar: {
    type: Array as PropType<typeof toolbarKeys>,
    default: () => toolbarKeys,
  },
  /**
   * 提示
   */
  placeholder: {
    type: String,
    default: "请输入内容...",
  },
  /**
   * 是否只读
   */
  readOnly: {
    type: Boolean,
    default: false,
  },
  /**
   * 配置编辑器默认是否 focus ，默认为 true
   */
  autoFocus: {
    type: Boolean,
    default: true,
  },
  /**
   * 配置编辑器是否支持滚动，默认为 true 。注意，此时不要固定编辑框的高度，设置一个 min-height 即可。
   * 可将 scroll 设置为 false 的情况：编辑器高度自增、在线文档
   */
  scroll: {
    type: Boolean,
    default: true,
  },
  height: {
    type: [String, Number],
    default: "500px",
  },
  minHeight: {
    type: [String, Number],
    default: "300px",
  },
  /**
   * 模式，可使用simple简单模式
   */
  mode: {
    type: String as PropType<"default" | "simple">,
    default: "default",
  },
  /**
   * 文件上传配置项
   */
  uploadImageConfig: {
    type: Object as PropType<RichEditorUploadImageConfig>,
    required: true,
  },
});

// 空数据处理
const htmlVal = computed(() => editorVal.value || "");

// 编辑器实例，必须用 shallowRef
const editorRef = shallowRef();

// editor高度
const editorHeight = computed(() =>
  typeof props.height == "number" ? props.height + "px" : props.height
);
const editorMinHeight = computed(() =>
  props.minHeight == "number" ? props.minHeight + "px" : props.minHeight
);

// emits
const emit = defineEmits(["onCreated", "onDestroyed", "onFocus", "onBlur"]);

// 初始化
const handleCreated = (editor: unknown) => {
  editorRef.value = editor; // 记录 editor 实例，重要！
  emit("onCreated", editor);
};

// 监听内容变化
const handleChange = (editor: { getHtml: () => string }) => {
  editorVal.value = editor.getHtml();
};

// 工具栏配置
const toolbarConfig: Partial<IToolbarConfig> = {
  toolbarKeys: props.toolbar,
};

// 自定义编辑器提示
const customAlert = (s: string, t: string) => {
  switch (t) {
    case "success":
      ElMessage.success(s);
      break;
    case "info":
      ElMessage.info(s);
      break;
    case "warning":
      ElMessage.warning(s);
      break;
    case "error":
      ElMessage.error(s);
      break;
    default:
      ElMessage.info(s);
      break;
  }
};

// 自定义链接格式校验
const customCheckLinkFn = (
  text: string,
  url: string
): string | boolean | undefined => {
  if (!url) {
    return;
  }
  if (!REG_EXP_PATTERN.url.test(url)) {
    return "链接格式错误，正确格式如 http://abc.com 或 https://abc.com";
  }
  return true;
};

const uploadImage = Object.assign(
  {
    fieldName: "file",
    withCredentials: false, //  是否跨域携带 cookie（默认 false）
    maxFileSize: 10 * 1024 * 1024, // 10MB
    allowedFileTypes: ["image/jpeg", "image/png", "image/gif", "image/svg+xml"], // 图片格式MIME类型
    base64LimitSize: 60 * 1024, // 60KB 以下转 Base64
    // 关键钩子：请求成功时处理返回数据
    // onSuccess: (file: File, res: any) => {
    //   // 1. 转换非标准响应格式
    //   // 假设后端返回 { code:200, imageUrl:'...' }
    //   if (res.code == 200) {
    //     return {
    //       errno: 0, // 必须返回 errno:0 表示成功
    //       data: [res.data.url], // 将 imageUrl 转换为标准 data 数组
    //     };
    //   }

    //   // 2. 多图上传处理（如返回 { url: ['a.jpg','b.jpg'] }）
    //   if (res.data.url?.length) {
    //     return { errno: 0, data: res.data.url };
    //   }

    //   // 3. 返回原始数据（若已符合 {errno:0, data:[url]} 格式）
    //   return res;
    // },
    // 错误处理（可选）
    onError: (file: File, err: any, res: any) => {
      console.error("上传失败", res);
      ElMessage.error("上传失败");
    },
    // 单个文件上传失败
    onFailed(file: File, res: any) {
      console.log(`${file.name} 上传失败`, res);
      ElMessage.error(`${file.name} 上传失败`);
    },
    customInsert: (
      res: any,
      insertFn: (url: string, alt?: string, href?: string) => void
    ) => {
      // 从 res 中提取 url 插入图片到编辑器
      const url = props.uploadImageConfig.callback(res) as string;
      if (url) {
        insertFn(url);
      } else {
        console.error("上传失败:", res);
        ElMessage.error("上传失败!");
      }
    },
  },
  props.uploadImageConfig
);

// 编辑栏配置()
const editorConfig = {
  placeholder: props.placeholder,
  readOnly: props.readOnly,
  autoFocus: props.autoFocus,
  scroll: props.scroll,
  // 菜单配置
  MENU_CONF: {
    // 插入链接
    insertLink: {
      // 链接校验
      checkLink: customCheckLinkFn,
    },
    // 编辑链接
    editLink: {
      // 链接校验
      checkLink: customCheckLinkFn,
    },
    // 上传图片配置
    uploadImage,
  },
};

// 组件销毁时，也及时销毁编辑器
onBeforeUnmount(() => {
  const editor = editorRef.value;
  if (editor == null) return;
  editor.destroy();
});
</script>

<style scoped lang="scss">
.my-rich-editor {
  width: 100%;
  border: 1px solid var(--el-border-color);
  border-radius: var(--el-border-radius-base);
  overflow: hidden;
  .editor-toolbar {
    border-bottom: 1px solid var(--el-border-color);
    border-start-start-radius: var(--el-border-radius-base);
    border-start-end-radius: var(--el-border-radius-base);
  }
  .editor-container {
    overflow-y: hidden;
    height: v-bind(editorHeight) !important;
    min-height: v-bind(editorMinHeight);
    border-end-start-radius: var(--el-border-radius-base);
    border-end-end-radius: var(--el-border-radius-base);
  }
}
:global(.w-e-full-screen-container) {
  z-index: 1000;
}
:global(.w-e-text-placeholder) {
  top: 10px;
}
</style>
